오늘 풀어본 문제는 백준의 1005번 문제1이다. 문제 풀이에 사용한 언어는 C++ 이다.
이 문제의 내용과 조건은 다음과 같다.
서기 $2012$ 년! 드디어 $2$ 년간 수많은 국민들을 기다리게 한 게임 ACM Craft (Association of Construction Manager Craft)가 발매되었다.
이 게임은 지금까지 나온 게임들과는 다르게 ACM크래프트는 다이나믹한 게임 진행을 위해 건물을 짓는 순서가 정해져 있지 않다. 즉, 첫 번째 게임과 두 번째 게임이 건물을 짓는 순서가 다를 수도 있다. 매 게임시작 시 건물을 짓는 순서가 주어진다. 또한 모든 건물은 각각 건설을 시작하여 완성이 될 때까지 Delay가 존재한다.
위의 예시를 보자.
이번 게임에서는 다음과 같이 건설 순서 규칙이 주어졌다. $1$ 번 건물의 건설이 완료된다면 $2$ 번과 $3$ 번의 건설을 시작할수 있다. (동시에 진행이 가능하다) 그리고 $4$ 번 건물을 짓기 위해서는 $2$ 번과 $3$ 번 건물이 모두 건설 완료되어야지만 $4$ 번건물의 건설을 시작할수 있다.
따라서 $4$ 번건물의 건설을 완료하기 위해서는 우선 처음 $1$ 번 건물을 건설하는데 $10$ 초가 소요된다. 그리고 $2$ 번 건물과 $3$ 번 건물을 동시에 건설하기 시작하면 $2$ 번은 $1$ 초뒤에 건설이 완료되지만 아직 $3$ 번 건물이 완료되지 않았으므로 $4$ 번 건물을 건설할 수 없다. $3$ 번 건물이 완성되고 나면 그때 $4$ 번 건물을 지을수 있으므로 $4$ 번 건물이 완성되기까지는 총 $120$ 초가 소요된다.
프로게이머 최백준은 애인과의 데이트 비용을 마련하기 위해 서강대학교배 ACM크래프트 대회에 참가했다! 최백준은 화려한 컨트롤 실력을 가지고 있기 때문에 모든 경기에서 특정 건물만 짓는다면 무조건 게임에서 이길 수 있다. 그러나 매 게임마다 특정건물을 짓기 위한 순서가 달라지므로 최백준은 좌절하고 있었다. 백준이를 위해 특정건물을 가장 빨리 지을 때까지 걸리는 최소시간을 알아내는 프로그램을 작성해주자.
첫째 줄에는 테스트케이스의 개수 $T$ 가 주어진다. 각 테스트 케이스는 다음과 같이 주어진다. 첫째 줄에 건물의 개수 $N$ 과 건물간의 건설순서 규칙의 총 개수 $K$ 이 주어진다. (건물의 번호는 $1$ 번부터 $N$ 번까지 존재한다)
둘째 줄에는 각 건물당 건설에 걸리는 시간 $D_1, D_2, …, D_N$ 이 공백을 사이로 주어진다. 셋째 줄부터 $K+2$ 줄까지 건설순서 $X\ Y$ 가 주어진다. (이는 건물 $X$ 를 지은 다음에 건물 $Y$ 를 짓는 것이 가능하다는 의미이다)
마지막 줄에는 백준이가 승리하기 위해 건설해야 할 건물의 번호 W가 주어진다.
건물 $W$ 를 건설완료 하는데 드는 최소 시간을 출력한다. 편의상 건물을 짓는 명령을 내리는 데는 시간이 소요되지 않는다고 가정한다.
건설순서는 모든 건물이 건설 가능하도록 주어진다.
문제를 보고 위상 정렬을 활용해야 하는 문제라는 것을 알 수 있었다. 해결하기 위해 떠올린 방법은 다음과 같다.
각 테스트 케이스에 대해서 입력을 받는다. 이 때 그래프를 인접 리스트 형태로 저장한다.
위상 정렬을 수행하면서 이전에 지어야할 건물의 최대 소요시간과 현재 건물을 짓는데 걸리는 시간을 더한 값을 다른 정점에서 오는 경우와 비교하여 더 큰 값을 저장한다.
만약 업데이트된 정점이 목표 건물이고, 해당 건물에 도달하기 위한 모든 선행 건물들이 이미 처리되었다면, 반복문을 즉시 탈출한다.
정답을 출력한다.
코드는 다음과 같이 작성하였다.
#include <bits/stdc++.h>
using namespace std;
void simulate();
int main(void) {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int T;
cin >> T;
for (int i=0; i<T; i++) {
simulate();
}
return 0;
}
void simulate() {
int N, K, W;
cin >> N >> K;
vector<int> buildingTime(N + 1);
vector<int> result(N + 1, -1);
vector<vector<int>> graph(N+1);
vector<int> entryDeg(N+1, 0);
for (int i=1; i<=N; i++) {
cin >> buildingTime[i];
}
for (int i=0; i<K; i++) {
int A, B;
cin >> A >> B;
graph[A].emplace_back(B);
entryDeg[B]++;
}
cin >> W;
queue<int> q;
for (int i=1; i<=N; i++) {
if (entryDeg[i] == 0) {
q.push(i);
result[i] = buildingTime[i];
}
}
bool resultFound = false;
while (!q.empty() && !resultFound) {
int curr = q.front();
q.pop();
for (auto next : graph[curr]) {
entryDeg[next]--;
result[next] = max(result[next], result[curr] + buildingTime[next]);
if (entryDeg[next] == 0) {
q.push(next);
if (next == W) {
resultFound = true;
break;
}
}
}
}
cout << result[W] << "\n";
}
그러자 모든 테스트 케이스를 통과하고 정답이 나오는 것을 확인할 수 있었다.
뭔가 오늘은 한 방에 성공해서 기분이 좋았다. 상상도 못한 반례나 오류가 있을줄 알고 가슴 졸이고 있었는데, 오늘은 그런게 없었던 것이다! 매일 이랬으면 좋겠다.
그리고 오늘도 위상 정렬이라는 용어가 등장했는데, 위상수학과는 아직도 무슨 관련이 있는지는 잘 모르겠다. 아마 지금 공부하는 것 보다 더 심화된 위상수학에서 관련성을 찾을 수 있을지도 모르겠다.
오늘의 PS는 여기까지!
1: https://www.acmicpc.net/problem/1005